// See LICENSE for license details.
package sifive.fpgashells.ip.xilinx

import Chisel._
import chisel3.{Input, Output}
import chisel3.experimental.{Analog, attach}
import freechips.rocketchip.util.{ElaborationArtefacts}

import sifive.blocks.devices.pinctrl.{BasePin}
import sifive.fpgashells.clocks._
import freechips.rocketchip.diplomacy.LazyModule

class JTAGTUNNEL extends BlackBox { 
  val io = new Bundle { 
    val jtag_tck = Output(Bool()) 
    val jtag_tms = Output(Bool()) 
    val jtag_tdi = Output(Bool()) 
    val jtag_tdo = Input(Bool()) 
    val jtag_tdo_en = Input(Bool()) 
  } 
  //setInline("JTAGTUNNEL.v", 
  //  s""" 
  ElaborationArtefacts.add("JTAGTUNNEL.v", 
    """ 
      |module JTAGTUNNEL( 
      |    output jtag_tck, 
      |    output jtag_tms, 
      |    output jtag_tdi, 
      |    input jtag_tdo, 
      |    input jtag_tdo_en 
      |); 
      |  
      |parameter USER_CHAIN=4; 
      |wire CAPTURE; 
      |wire DRCK; 
      |wire RESET; 
      |wire SEL; 
      |wire SHIFT; 
      |wire UPDATE; 
      |wire RUNTEST; 
      | 
      |wire jtag_tck; 
      |reg  jtag_tms; 
      |wire jtag_tdi; 
      |wire jtag_tdo; 
      |wire jtag_tdo_en; 
      |wire TDO; 
      |wire reset; 
      |wire start_tck; 
      |reg [6:0] shiftreg_cnt; 
      |reg [7:0] counter_neg; 
      |reg [7:0] counter_pos; 
      |reg         TDI_REG; 
      | 
      |BSCANE2 #( 
      |  .JTAG_CHAIN(USER_CHAIN) // Value for USER command. 
      |) 
      |BSCANE2_inst ( 
      |  .CAPTURE(CAPTURE), // 1-bit output: CAPTURE output from TAP controller. 
      |  .DRCK(DRCK), // 1-bit output: Gated TCK output. When SEL is asserted, DRCK toggles when CAPTURE or 
      |  // SHIFT are asserted. 
      |    .RESET(RESET), // 1-bit output: Reset output for TAP controller. 
      |    .RUNTEST(RUNTEST), // 1-bit output: Output asserted when TAP controller is in Run Test/Idle state. 
      |    .SEL(SEL), // 1-bit output: USER instruction active output. 
      |    .SHIFT(SHIFT), // 1-bit output: SHIFT output from TAP controller. 
      |    .TCK(TCK), // 1-bit output: Test Clock output. Fabric connection to TAP Clock pin. 
      |    .TDI(TDI), // 1-bit output: Test Data Input (TDI) output from TAP controller. 
      |    .TMS(TMS), // 1-bit output: Test Mode Select output. Fabric connection to TAP. 
      |    .UPDATE(UPDATE), // 1-bit output: UPDATE output from TAP controller 
      |    .TDO(TDO) // 1-bit input: Test Data Output (TDO) input for USER function. 
      |  ); 
      |// 
      |BUFGCE BUFGCE_JTAG ( 
      |  .O(jtag_tck), // 1-bit output: Clock output 
      |  .CE(start_tck), // 1-bit input: Clock enable input for I0 
      |  .I(TCK) // 1-bit input: Primary clock 
      |); 
      | 
      | 
      |assign start_tck = SEL; 
      |assign jtag_tdi = TDI; 
      |assign TDO = jtag_tdo_en ? jtag_tdo : 1'b1; 
      | 
      |always@(*) begin 
      |        if (counter_neg == 8'h04) begin 
      |                jtag_tms = TDI_REG; 
      |        end else if (counter_neg == 8'h05) begin 
      |                jtag_tms = 1'b1; 
      |        end else if ((counter_neg == (8'h08 + shiftreg_cnt)) || (counter_neg == (8'h08 + shiftreg_cnt - 8'h01))) begin 
      |                jtag_tms = 1'b1; 
      |        end else begin 
      |                jtag_tms = 1'b0; 
      |        end 
      |end 
      | 
      |always@(posedge TCK) begin 
      |        if (~SHIFT) begin 
      |                shiftreg_cnt <= 7'b0000000; 
      |        end else if ((counter_pos >= 8'h01) && (counter_pos <= 8'h07))  begin 
      |                shiftreg_cnt <= {{TDI, shiftreg_cnt[6:1]}}; 
      |        end else begin 
      |                shiftreg_cnt <= shiftreg_cnt; 
      |        end 
      |end 
      | 
      |always@(posedge TCK) begin 
      |        if (~SHIFT) begin 
      |                TDI_REG <= 1'b0; 
      |        end else if (counter_pos == 8'h00) begin 
      |                TDI_REG <= ~TDI; 
      |        end else begin 
      |                TDI_REG <= TDI_REG; 
      |        end 
      |end 
      | 
      |always@(negedge TCK) begin 
      |        if (~SHIFT) begin 
      |                counter_neg <= 8'b00000000; 
      |        end else begin 
      |                counter_neg <= counter_neg + 1; 
      |        end 
      |end 
      |  
      |always@(posedge TCK) begin 
      |        if (~SHIFT) begin 
      |                counter_pos <= 8'b00000000; 
      |        end else begin 
      |                counter_pos <= counter_pos + 1; 
      |        end 
      |end 
      |endmodule 
    """.stripMargin) 
}

object JTAGTUNNEL { 
 
    def apply (DUT_TCK: Bool, DUT_TMS: Bool, DUT_TDI: Bool, DUT_TDO:Bool, DUT_TDO_en: Bool): Unit = { 
      val inst_jtag_tunnel = Module(new JTAGTUNNEL())
      DUT_TCK := inst_jtag_tunnel.io.jtag_tck 
      DUT_TMS := inst_jtag_tunnel.io.jtag_tms 
      DUT_TDI := inst_jtag_tunnel.io.jtag_tdi
      inst_jtag_tunnel.io.jtag_tdo := DUT_TDO 
      inst_jtag_tunnel.io.jtag_tdo_en := DUT_TDO_en
  }
}

//========================================================================
// This file contains common devices used by our Xilinx FPGA flows and some
// BlackBox modules used in the Xilinx FPGA flows
//========================================================================

//-------------------------------------------------------------------------
// mmcm
//-------------------------------------------------------------------------
/** mmcm: This is generated by the Xilinx IP Generation Scripts */

class mmcm extends BlackBox {
  val io = new Bundle {
    val clk_in1  = Input(Clock())
    val clk_out1 = Output(Clock())
    val clk_out2 = Output(Clock())
    val clk_out3 = Output(Clock())
    val resetn   = Input(Bool())
    val locked   = Output(Bool())
  }
}

//-------------------------------------------------------------------------
// reset_sys
//-------------------------------------------------------------------------
/** reset_sys: This is generated by the Xilinx IP Generation Scripts */

class reset_sys extends BlackBox {
  val io = new Bundle {
    val slowest_sync_clk     = Input(Clock())
    val ext_reset_in         = Input(Bool())
    val aux_reset_in         = Input(Bool())
    val mb_debug_sys_rst     = Input(Bool())
    val dcm_locked           = Input(Bool())
    val mb_reset             = Output(Bool())
    val bus_struct_reset     = Output(Bool())
    val peripheral_reset     = Output(Bool())
    val interconnect_aresetn = Output(Bool())
    val peripheral_aresetn   = Output(Bool())
  }
}

//-------------------------------------------------------------------------
// reset_mig
//-------------------------------------------------------------------------
/** reset_mig: This is generated by the Xilinx IP Generation Scripts */

class reset_mig extends BlackBox {
  val io = new Bundle {
    val slowest_sync_clk     = Input(Clock())
    val ext_reset_in         = Input(Bool())
    val aux_reset_in         = Input(Bool())
    val mb_debug_sys_rst     = Input(Bool())
    val dcm_locked           = Input(Bool())
    val mb_reset             = Output(Bool())
    val bus_struct_reset     = Output(Bool())
    val peripheral_reset     = Output(Bool())
    val interconnect_aresetn = Output(Bool())
    val peripheral_aresetn   = Output(Bool())
  }
}

//-------------------------------------------------------------------------
// PowerOnResetFPGAOnly
//-------------------------------------------------------------------------
/** PowerOnResetFPGAOnly -- this generates a power_on_reset signal using
  * initial blocks.  It is synthesizable on FPGA flows only.
  */

// This is a FPGA-Only construct, which uses
// 'initial' constructions
class PowerOnResetFPGAOnly extends BlackBox {
  val io = new Bundle {
    val clock = Input(Clock())
    val power_on_reset = Output(Bool())
  }
}

object PowerOnResetFPGAOnly {
  def apply (clk: Clock, name: String): Bool = {
    val por = Module(new PowerOnResetFPGAOnly())
    por.suggestName(name)
    por.io.clock := clk
    por.io.power_on_reset
  }
  def apply (clk: Clock): Bool = apply(clk, "fpga_power_on")
}


//-------------------------------------------------------------------------
// vc707_sys_clock_mmcm
//-------------------------------------------------------------------------
//IP : xilinx mmcm with "NO_BUFFER" input clock
class Series7MMCM(c : PLLParameters) extends BlackBox with PLLInstance {
  val io = new Bundle {
    val clk_in1   = Clock(INPUT)
    val clk_out1  = if (c.req.size >= 1) Some(Clock(OUTPUT)) else None
    val clk_out2  = if (c.req.size >= 2) Some(Clock(OUTPUT)) else None
    val clk_out3  = if (c.req.size >= 3) Some(Clock(OUTPUT)) else None
    val clk_out4  = if (c.req.size >= 4) Some(Clock(OUTPUT)) else None
    val clk_out5  = if (c.req.size >= 5) Some(Clock(OUTPUT)) else None
    val clk_out6  = if (c.req.size >= 6) Some(Clock(OUTPUT)) else None
    val clk_out7  = if (c.req.size >= 7) Some(Clock(OUTPUT)) else None
    val reset     = Bool(INPUT)
    val locked    = Bool(OUTPUT)
  }

  val moduleName = c.name
  override def desiredName = c.name

  def getClocks = Seq() ++ io.clk_out1 ++ io.clk_out2 ++ 
                           io.clk_out3 ++ io.clk_out4 ++ 
                           io.clk_out5 ++ io.clk_out6 ++ 
                           io.clk_out7
  def getInput = io.clk_in1
  def getReset = Some(io.reset)
  def getLocked = io.locked
  def getClockNames = Seq.tabulate (c.req.size) { i =>
    s"${c.name}/inst/mmcm_adv_inst/CLKOUT${i}"
  }

  val used = Seq.tabulate(7) { i =>
    s" CONFIG.CLKOUT${i+1}_USED {${i < c.req.size}} \\\n"
  }.mkString

  val outputs = c.req.zipWithIndex.map { case (r, i) =>
    s""" CONFIG.CLKOUT${i+1}_REQUESTED_OUT_FREQ {${r.freqMHz}} \\
       | CONFIG.CLKOUT${i+1}_REQUESTED_PHASE {${r.phaseDeg}} \\
       | CONFIG.CLKOUT${i+1}_REQUESTED_DUTY_CYCLE {${r.dutyCycle}} \\
       |""".stripMargin
  }.mkString

  val checks = c.req.zipWithIndex.map { case (r, i) =>
    val f = if (i == 0) "_F" else ""
    val phaseMin = r.phaseDeg - r.phaseErrorDeg
    val phaseMax = r.phaseDeg + r.phaseErrorDeg
    val freqMin = r.freqMHz * (1 - r.freqErrorPPM / 1000000)
    val freqMax = r.freqMHz * (1 + r.freqErrorPPM / 1000000)
    s"""set jitter [get_property CONFIG.CLKOUT${i+1}_JITTER [get_ips ${moduleName}]]
       |if {$$jitter > ${r.jitterPS}} {
       |  puts "Output jitter $$jitter ps exceeds required limit of ${r.jitterPS}"
       |  exit 1
       |}
       |set phase [get_property CONFIG.MMCM_CLKOUT${i}_PHASE [get_ips ${moduleName}]]
       |if {$$phase < ${phaseMin} || $$phase > ${phaseMax}} {
       |  puts "Achieved phase $$phase degrees is outside tolerated range ${phaseMin}-${phaseMax}"
       |  exit 1
       |}
       |set div2 [get_property CONFIG.MMCM_CLKOUT${i}_DIVIDE${f} [get_ips ${moduleName}]]
       |set freq [expr { ${c.input.freqMHz} * $$mult / $$div1 / $$div2 }]
       |if {$$freq < ${freqMin} || $$freq > ${freqMax}} {
       |  puts "Achieved frequency $$freq MHz is outside tolerated range ${freqMin}-${freqMax}"
       |  exit 1
       |}
       |puts "Achieve frequency $$freq MHz phase $$phase degrees jitter $$jitter ps"
       |""".stripMargin
  }.mkString


  val aligned = if (c.input.feedback) " CONFIG.USE_PHASE_ALIGNMENT {true} \\\n" else ""

  ElaborationArtefacts.add(s"${moduleName}.vivado.tcl",
    s"""create_ip -name clk_wiz -vendor xilinx.com -library ip -module_name \\
       | ${moduleName} -dir $$ipdir -force
       |set_property -dict [list \\
       | CONFIG.CLK_IN1_BOARD_INTERFACE {Custom} \\
       | CONFIG.PRIM_SOURCE {No_buffer} \\
       | CONFIG.NUM_OUT_CLKS {${c.req.size.toString}} \\
       | CONFIG.PRIM_IN_FREQ {${c.input.freqMHz.toString}} \\
       | CONFIG.CLKIN1_JITTER_PS {${c.input.jitter}} \\
       |${used}${aligned}${outputs}] [get_ips ${moduleName}]
       |set mult [get_property CONFIG.MMCM_CLKFBOUT_MULT_F [get_ips ${moduleName}]]
       |set div1 [get_property CONFIG.MMCM_DIVCLK_DIVIDE [get_ips ${moduleName}]]
       |${checks}""".stripMargin)
}

//-------------------------------------------------------------------------
// vc707reset
//-------------------------------------------------------------------------

class vc707reset() extends BlackBox
{
  val io = new Bundle{
    val areset = Bool(INPUT)
    val clock1 = Clock(INPUT)
    val reset1 = Bool(OUTPUT)
    val clock2 = Clock(INPUT)
    val reset2 = Bool(OUTPUT)
    val clock3 = Clock(INPUT)
    val reset3 = Bool(OUTPUT)
    val clock4 = Clock(INPUT)
    val reset4 = Bool(OUTPUT)
  }
}

//-------------------------------------------------------------------------
// vcu118_sys_clock_mmcm
//-------------------------------------------------------------------------
//IP : xilinx mmcm with "NO_BUFFER" input clock

class vcu118_sys_clock_mmcm0 extends BlackBox {
  val io = new Bundle {
    val clk_in1   = Bool(INPUT)
    val clk_out1  = Clock(OUTPUT)
    val clk_out2  = Clock(OUTPUT)
    val clk_out3  = Clock(OUTPUT)
    val clk_out4  = Clock(OUTPUT)
    val clk_out5  = Clock(OUTPUT)
    val clk_out6  = Clock(OUTPUT)
    val clk_out7  = Clock(OUTPUT)
    val reset     = Bool(INPUT)
    val locked    = Bool(OUTPUT)
  }

  ElaborationArtefacts.add(
    "vcu118_sys_clock_mmcm0.vivado.tcl",
    """create_ip -name clk_wiz -vendor xilinx.com -library ip -module_name vcu118_sys_clock_mmcm0 -dir $ipdir -force
    set_property -dict [list \
    CONFIG.CLK_IN1_BOARD_INTERFACE {Custom} \
    CONFIG.PRIM_SOURCE {No_buffer} \
    CONFIG.CLKOUT2_USED {true} \
    CONFIG.CLKOUT3_USED {true} \
    CONFIG.CLKOUT4_USED {true} \
    CONFIG.CLKOUT5_USED {true} \
    CONFIG.CLKOUT6_USED {true} \
    CONFIG.CLKOUT7_USED {true} \
    CONFIG.CLKOUT1_REQUESTED_OUT_FREQ {12.5} \
    CONFIG.CLKOUT2_REQUESTED_OUT_FREQ {25} \
    CONFIG.CLKOUT3_REQUESTED_OUT_FREQ {37.5} \
    CONFIG.CLKOUT4_REQUESTED_OUT_FREQ {50} \
    CONFIG.CLKOUT5_REQUESTED_OUT_FREQ {100} \
    CONFIG.CLKOUT6_REQUESTED_OUT_FREQ {150.000} \
    CONFIG.CLKOUT7_REQUESTED_OUT_FREQ {75} \
    CONFIG.CLK_IN1_BOARD_INTERFACE {Custom} \
    CONFIG.PRIM_IN_FREQ {250.000} \
    CONFIG.CLKIN1_JITTER_PS {50.0} \
    CONFIG.MMCM_DIVCLK_DIVIDE {5} \
    CONFIG.MMCM_CLKFBOUT_MULT_F {24.000} \
    CONFIG.MMCM_CLKIN1_PERIOD {4.000} \
    CONFIG.MMCM_CLKOUT0_DIVIDE_F {96.000} \
    CONFIG.MMCM_CLKOUT1_DIVIDE {48} \
    CONFIG.MMCM_CLKOUT2_DIVIDE {32} \
    CONFIG.MMCM_CLKOUT3_DIVIDE {24} \
    CONFIG.MMCM_CLKOUT4_DIVIDE {12} \
    CONFIG.MMCM_CLKOUT5_DIVIDE {8} \
    CONFIG.MMCM_CLKOUT6_DIVIDE {16} \
    CONFIG.NUM_OUT_CLKS {7} \
    CONFIG.CLKOUT1_JITTER {213.008} \
    CONFIG.CLKOUT1_PHASE_ERROR {154.678} \
    CONFIG.CLKOUT2_JITTER {179.547} \
    CONFIG.CLKOUT2_PHASE_ERROR {154.678} \
    CONFIG.CLKOUT3_JITTER {164.187} \
    CONFIG.CLKOUT3_PHASE_ERROR {154.678} \
    CONFIG.CLKOUT4_JITTER {154.688} \
    CONFIG.CLKOUT4_PHASE_ERROR {154.678} \
    CONFIG.CLKOUT5_JITTER {135.165} \
    CONFIG.CLKOUT5_PHASE_ERROR {154.678} \
    CONFIG.CLKOUT6_JITTER {126.046} \
    CONFIG.CLKOUT6_PHASE_ERROR {154.678} \
    CONFIG.CLKOUT7_JITTER {142.781} \
    CONFIG.CLKOUT7_PHASE_ERROR {154.678}] [get_ips vcu118_sys_clock_mmcm0] """
  )
}

class vcu118_sys_clock_mmcm1 extends BlackBox {
  val io = new Bundle {
    val clk_in1   = Bool(INPUT)
    val clk_out1  = Clock(OUTPUT)
    val clk_out2  = Clock(OUTPUT)
    val reset     = Bool(INPUT)
    val locked    = Bool(OUTPUT)
  }

  ElaborationArtefacts.add(
    "vcu118_sys_clock_mmcm1.vivado.tcl",
    """create_ip -name clk_wiz -vendor xilinx.com -library ip -module_name vcu118_sys_clock_mmcm1 -dir $ipdir -force
    set_property -dict [list \
    CONFIG.CLK_IN1_BOARD_INTERFACE {Custom} \
    CONFIG.PRIM_SOURCE {No_buffer} \
    CONFIG.CLKOUT2_USED {true} \
    CONFIG.CLKOUT3_USED {false} \
    CONFIG.CLKOUT4_USED {false} \
    CONFIG.CLKOUT5_USED {false} \
    CONFIG.CLKOUT6_USED {false} \
    CONFIG.CLKOUT7_USED {false} \
    CONFIG.CLKOUT1_REQUESTED_OUT_FREQ {32.5} \
    CONFIG.CLKOUT2_REQUESTED_OUT_FREQ {65} \
    CONFIG.CLK_IN1_BOARD_INTERFACE {Custom} \
    CONFIG.PRIM_IN_FREQ {250.000} \
    CONFIG.CLKIN1_JITTER_PS {50.0} \
    CONFIG.MMCM_DIVCLK_DIVIDE {25} \
    CONFIG.MMCM_CLKFBOUT_MULT_F {117.000} \
    CONFIG.MMCM_CLKIN1_PERIOD {4.000} \
    CONFIG.MMCM_CLKOUT0_DIVIDE_F {36.000} \
    CONFIG.MMCM_CLKOUT1_DIVIDE {18} \
    CONFIG.MMCM_CLKOUT2_DIVIDE {1} \
    CONFIG.MMCM_CLKOUT3_DIVIDE {1} \
    CONFIG.MMCM_CLKOUT4_DIVIDE {1} \
    CONFIG.MMCM_CLKOUT5_DIVIDE {1} \
    CONFIG.MMCM_CLKOUT6_DIVIDE {1} \
    CONFIG.NUM_OUT_CLKS {2} \
    CONFIG.CLKOUT1_JITTER {257.594} \
    CONFIG.CLKOUT1_PHASE_ERROR {366.693} \
    CONFIG.CLKOUT2_JITTER {232.023} \
    CONFIG.CLKOUT2_PHASE_ERROR {366.693}] \
    [get_ips vcu118_sys_clock_mmcm1] """
  )
}

//-------------------------------------------------------------------------
// vcu118reset
//-------------------------------------------------------------------------

class vcu118reset() extends BlackBox
{
  val io = new Bundle{
    val areset = Bool(INPUT)
    val clock1 = Clock(INPUT)
    val reset1 = Bool(OUTPUT)
    val clock2 = Clock(INPUT)
    val reset2 = Bool(OUTPUT)
    val clock3 = Clock(INPUT)
    val reset3 = Bool(OUTPUT)
    val clock4 = Clock(INPUT)
    val reset4 = Bool(OUTPUT)
  }
}

//-------------------------------------------------------------------------
// sdio_spi_bridge
//-------------------------------------------------------------------------

class sdio_spi_bridge() extends BlackBox
{
  val io = new Bundle{
    val clk      = Clock(INPUT)
    val reset    = Bool(INPUT)
    val sd_cmd   = Analog(1.W)
    val sd_dat   = Analog(4.W)
    val spi_sck  = Bool(INPUT)
    val spi_cs   = Bool(INPUT)
    val spi_dq_o = Bits(INPUT,4)
    val spi_dq_i = Bits(OUTPUT,4)
  }
}
